#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <stack>
#include <string>

namespace fs = std::filesystem;

namespace WMLayoutSCSSParser {
struct LexicalToken {
    enum class TokenType {
        CHAR, // &:{/;#.
        NUMBER,
        STRING,
        UNKNOWN,
        END,
    } type;
    std::string token;
};

class LexicalAnalyzer {
public:
    void ParseSCSS(const std::filesystem::path &file);
    void IgnoreLine();
    struct LexicalToken GetNextToken();

private:
    bool GetNextChar();
    bool IsBlank();
    bool IsNumber();
    bool IsChar();
    bool IsString();

    std::ifstream ifs;
    char nowChar;
};

void LexicalAnalyzer::ParseSCSS(const std::filesystem::path &file)
{
    ifs.open(file);
    GetNextChar();
}

void LexicalAnalyzer::IgnoreLine()
{
    while (GetNextChar()) {
        if (nowChar == '\n') {
            GetNextChar();
            break;
        }
    }
}

bool LexicalAnalyzer::GetNextChar()
{
    if (ifs.eof()) {
        return false;
    }

    nowChar = ifs.get();
    return true;
}

bool LexicalAnalyzer::IsBlank()
{
    return nowChar == ' ' || nowChar == '\n';
}

bool LexicalAnalyzer::IsNumber()
{
    return nowChar == '.' || (nowChar >= '0' && nowChar <= '9');
}

bool LexicalAnalyzer::IsChar()
{
    return nowChar == '&' || nowChar == ':' || nowChar == '{' || nowChar == '}' ||
        nowChar == '/' || nowChar == ';' || nowChar == '#' || nowChar == '.';
}

bool LexicalAnalyzer::IsString()
{
    return nowChar == '-' || nowChar == '_' || (nowChar >= 'a' && nowChar <= 'z') ||
        (nowChar >= 'A' && nowChar <= 'Z');
}

struct LexicalToken LexicalAnalyzer::GetNextToken()
{
    struct LexicalToken ret = { .type = LexicalToken::TokenType::UNKNOWN, .token = "", };
    while (IsBlank()) {
        if (!GetNextChar()) {
            return ret;
        }
    }
    if (IsChar()) {
        ret.type = LexicalToken::TokenType::CHAR;
        ret.token = nowChar;
        GetNextChar();
        return ret;
    }

    if (IsString()) {
        ret.type = LexicalToken::TokenType::STRING;
        while (IsString()) {
            ret.token += nowChar;
            if (!GetNextChar()) {
                return ret;
            }
        }
        return ret;
    }

    if (IsNumber()) {
        ret.type = LexicalToken::TokenType::NUMBER;
        while (IsNumber()) {
            ret.token += nowChar;
            if (!GetNextChar()) {
                return ret;
            }
        }
        return ret;
    }

    if (nowChar == EOF) {
        ret.type = LexicalToken::TokenType::END;
    }
    return ret;
}

class Backend {
public:
    void EnterTypeScope(const std::string &type);
    void ExitTypeScope();

    void EnterModeScope(const std::string &mode);
    void ExitModeScope();

    void DefineAttribute(const std::string &name, const std::string &value);

private:
    std::string scopeType;
    std::string scopeMode;
    std::map<std::string, std::map<std::string, std::string>> attrMaps;
};

void Backend::EnterTypeScope(const std::string &type)
{
    scopeType = type;
}

void Backend::ExitTypeScope()
{
    scopeType = "";
}

void Backend::EnterModeScope(const std::string &mode)
{
    scopeMode = mode;
}

void Backend::ExitModeScope()
{
    scopeType = "";
}

void Backend::DefineAttribute(const std::string &name, const std::string &value)
{
    attrMaps[scopeType][name] = value;
}

/*
 * P is StartProducer
 * T is Statement
 * A is Attribute
 * S is String
 * N is Number
 * B is zero or one or more Attribute
 *
 * 1. P = T P
 * 2. T = # S { B }
 * 3. A = S C
 * 4. A = & : S { B }
 * 5. C = : D
 * 6. C = { B }
 * 7. D = S ;
 * 8. D = N ;
 * 9. B = A B
 * X. B =
 *
 * front(1) = #
 * front(2) = #
 * front(3) = S
 * front(4) = &
 * front(5) = :
 * front(6) = {
 * front(7) = S
 * front(8) = N
 * front(9) = S, &
 * front(X) = except(S, &)
*/
class GrammarAnalyzer {
public:
    Backend b;

    bool ParseSCSS(const std::filesystem::path &file)
    {
        la.ParseSCSS(file);
        stack.push('P');
        GetNextToken();
        if (curToken.type == LexicalToken::TokenType::UNKNOWN) {
            printf("%d\n", __LINE__);
            return false;
        }
        while (true) {
            if (!CleanComment()) {
                printf("%d\n", __LINE__);
                return false;
            }
            printf("cur: '%s'\n", curToken.token.c_str());
            switch (stack.top()) {
                case 'P':
                    if (curToken.token == "#") {
                        printf("1. P = T P\n");
                        stack.pop();
                        stack.push('P');
                        stack.push('T');
                    } else {
                        if (curToken.type == LexicalToken::TokenType::END) {
                            return true;
                        }
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
                case 'T':
                    if (curToken.token == "#") {
                        printf("2. T = # S { B }\n");
                        stack.pop();
                        stack.push('}');
                        stack.push('B');
                        stack.push('{');
                        stack.push('S');
                        stack.push('#');
                    } else {
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
                case 'A':
                    if (curToken.type == LexicalToken::TokenType::STRING) {
                        printf("3. A = S C\n");
                        stack.pop();
                        stack.push('C');
                        stack.push('S');
                    } else if (curToken.token == "&") {
                        printf("4. A = & : S { B }\n");
                        stack.pop();
                        stack.push('}');
                        stack.push('B');
                        stack.push('{');
                        stack.push('S');
                        stack.push(':');
                        stack.push('&');
                    } else {
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
                case 'C':
                    if (curToken.token == ":") {
                        printf("5. C = : D\n");
                        stack.pop();
                        stack.push('D');
                        stack.push(':');
                    } else if (curToken.token == "{") {
                        printf("6. C = { B }\n");
                        stack.pop();
                        stack.push('}');
                        stack.push('B');
                        stack.push('{');
                    } else {
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
                case 'D':
                    if (curToken.type == LexicalToken::TokenType::STRING) {
                        printf("7. D = S ;\n");
                        stack.pop();
                        stack.push(';');
                        stack.push('S');
                    } else if (curToken.type == LexicalToken::TokenType::NUMBER) {
                        printf("8. D = N ;\n");
                        stack.pop();
                        stack.push(';');
                        stack.push('N');
                    } else {
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
                case 'B':
                    if (curToken.type == LexicalToken::TokenType::STRING || curToken.token == "&") {
                        printf("9. B = A B\n");
                        stack.pop();
                        stack.push('B');
                        stack.push('A');
                    } else {
                        printf("X. B =\n");
                        stack.pop();
                    }
                    break;
                case 'S':
                    if (curToken.type == LexicalToken::TokenType::STRING) {
                        printf("* S\n");
                        stack.pop();
                        GetNextToken();
                    } else {
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
                case 'N':
                    if (curToken.type == LexicalToken::TokenType::NUMBER) {
                        printf("* N\n");
                        stack.pop();
                        GetNextToken();
                    } else {
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
                default:
                    if (curToken.token.length() > 0 && curToken.token[0] == stack.top()) {
                        printf("* %s\n", curToken.token.c_str());
                        stack.pop();
                        GetNextToken();
                    } else {
                        printf("%d\n", __LINE__);
                        return false;
                    }
                    break;
            }
        }
    }

private:
    void GetNextToken()
    {
        curToken = la.GetNextToken();
    }

    bool CleanComment()
    {
        while (curToken.token == "/") {
            GetNextToken();
            if (curToken.token == "/") {
                la.IgnoreLine();
                GetNextToken();
                printf("ignore\n");
            } else {
                printf("%d\n", __LINE__);
                return false;
            }
        }
        return true;
    }

    LexicalAnalyzer la;
    std::stack<char> stack;
    LexicalToken curToken;
};

class Engine {
public:
    void ParseSCSS(const std::filesystem::path &file)
    {
        ga.ParseSCSS(file);
    }

private:
    GrammarAnalyzer ga;
};
}

int main()
{
    fs::path dir{"/home/luwh/testcss"};
    if (!fs::exists(dir)) {
        // not exist dir
        return 0;
    }

    fs::directory_entry entry{dir};
    if (entry.status().type() != fs::file_type::directory) {
        // not dir
        return 0;
    }

    WMLayoutSCSSParser::Engine engine;
    fs::directory_iterator files{dir};
    for (const auto &file : files) {
        if (file.is_regular_file() && file.path().extension() == ".scss") {
            engine.ParseSCSS(file.path());
        }
    }
    return 0;
}
